// Package migrate  操作对象
// 负责创建 migrations 数据表，以及执行迁移操作。
package migrate

import (
	"os"

	"gorm.io/gorm"

	"github.com/runningwater/gohub/pkg/console"
	"github.com/runningwater/gohub/pkg/database"
	"github.com/runningwater/gohub/pkg/file"
)

// Migrator 结构体用于存储迁移器的相关信息。
type Migrator struct {
	Folder   string        // Folder 是存储迁移文件的目录名。
	DB       *gorm.DB      // Db 是数据库连接对象。
	Migrator gorm.Migrator // Migrator 是 GORM 的迁移器对象。
}

func (m *Migrator) createMigrationsTable() {
	migration := Migration{}

	if !m.Migrator.HasTable(&migration) {
		// 如果表不存在，则创建表
		if err := m.DB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci").Migrator().CreateTable(&migration); err != nil {
			console.ExitIf(err)
			return
		}
	}
}

// Migration 对应数据表 migrations 里的一条记录
type Migration struct {
	ID        uint64 `gorm:"primaryKey;autoIncrement;"`
	Migration string `gorm:"type:varchar(255);not null;unique;"`
	Batch     int
}

func NewMigrator() *Migrator {
	migrator := &Migrator{
		Folder:   "database/migrations",
		DB:       database.DB,
		Migrator: database.DB.Migrator(),
	}

	migrator.createMigrationsTable()

	return migrator
}

// Up 执行所有未执行的迁移文件。
func (m *Migrator) Up() {

	// 读取所有迁移文件， 确保按照时间排序
	migrateFiles := m.readAllMigrationFiles()
	batch := m.getBatch()

	// 获取所有迁移数据
	var migrations []Migration
	m.DB.Find(&migrations)

	// 可以通过此值来判断数据库是否已是最新
	runed := false

	// 遍历迁移文件，执行未执行的迁移文件
	for _, mfile := range migrateFiles {

		if mfile.isNotMigrated(migrations) {
			m.runUpMigration(mfile, batch)
			runed = true
		}
	}

	if !runed {
		console.Success("database is up to date")
	}
}

// Rollback 回滚上一次的迁移操作
func (m *Migrator) Rollback() {

	// 获取最后一批次的迁移数据
	lastMigration := Migration{}
	m.DB.Order("id desc").First(&lastMigration)
	var migrations []Migration
	m.DB.Where("batch = ?", lastMigration.Batch).Order("id desc").Find(&migrations)

	// 回滚迁移操作
	if !m.rollbackMigrations(migrations) {
		console.Success("[migrations] table is empty, nothing to rollback")
	}
}

// Reset 回滚所有的迁移操作
func (m *Migrator) Reset() {

	// 获取所有的迁移数据
	var migrations []Migration
	m.DB.Order("id desc").Find(&migrations)

	// 回滚迁移操作
	if !m.rollbackMigrations(migrations) {
		console.Success("[migrations] table is empty, nothing to rollback")
	}
}

// Refresh 回滚所有的迁移操作， 并重新执行所有的迁移操作
func (m *Migrator) Refresh() {
	// 回滚所有的迁移操作
	m.Reset()
	// 重新执行所有的迁移操作
	m.Up()
}

// Fresh Drop 所有的表， 并重新执行所有的迁移操作
func (m *Migrator) Fresh() {

	// 获取数据库名称，用以提示
	dbname := database.CurrentDatabase()

	// 删除所有表
	err := database.DeleteAllTables()
	console.ExitIf(err)
	console.Success("database " + dbname + " cleared")

	// 重新创建 migrates 表
	m.createMigrationsTable()
	console.Success("migrations table created")

	// 重新执行所有的迁移操作
	m.Up()
}

// 回滚迁移操作
func (m *Migrator) rollbackMigrations(migrations []Migration) bool {

	// 标记是否真的有执行了迁移回退的操作
	runed := false
	// 遍历迁移数据，回滚迁移操作
	for _, migration := range migrations {
		// 友好提示
		console.Warning("rolling back " + migration.Migration + " ...")
		// 获取迁移文件
		mfile := getMigrationFile(migration.Migration)
		if mfile.Down != nil {
			// 执行迁移回退操作
			mfile.Down(database.DB.Migrator(), database.DB)
		}
		runed = true

		// 删除迁移数据
		m.DB.Delete(&migration)
		console.Success("rolled back " + migration.Migration + " finished")
	}

	return runed
}

// 获取当前这个批次的值
func (m *Migrator) getBatch() int {

	batch := 1

	lastMigration := Migration{}
	m.DB.Order("id desc").First(&lastMigration)

	// 如果有值的话，加一
	if lastMigration.ID > 0 {
		batch = lastMigration.Batch + 1
	}

	return batch
}

// 从文件目录读取文件，保证正确的时间排序
func (m *Migrator) readAllMigrationFiles() []MigrationFile {
	// 读取 database/migrations 目录下的所有迁移文件
	files, err := os.ReadDir(m.Folder)
	console.ExitIf(err)

	var migrateFiles []MigrationFile
	// 遍历所有迁移文件
	for _, f := range files {

		// 去除文件后缀 .go
		fileName := file.NameWithoutExtension(f.Name())
		// 通过迁移文件名称获取[MigrationFile]结构体
		mfile := getMigrationFile(fileName)
		if len(mfile.FileName) > 0 {
			migrateFiles = append(migrateFiles, mfile)
		}
	}
	return migrateFiles
}

// 插入一条记录到数据库，同时执行迁移操作
// 此方法会在 Up 方法中被调用
// mfile 是迁移文件的结构体，batch 是批次号，用于区分不同的迁移批次。
// 此方法会执行迁移操作，同时插入一条记录到数据库。
// 如果迁移操作成功，会输出一条成功信息；如果迁移操作失败，会输出一条错误信息，并退出程序。
func (m *Migrator) runUpMigration(file MigrationFile, batch int) {

	if file.Up != nil {
		console.Warning("migrating " + file.FileName)
		// 执行迁移操作
		file.Up(database.DB.Migrator(), database.DB)
		console.Success("migrated " + file.FileName)
	}
	// 插入一条记录到数据库
	err := m.DB.Create(&Migration{
		Migration: file.FileName,
		Batch:     batch,
	}).Error
	console.ExitIf(err)
}
